#! /usr/bin/env node
const fs = require('fs');
const path = require('path');
const jsdoc = require('jsdoc-api');
const cp = require('comment-parser');

/** VUE的规则是深度优先，继承优先 */
function componentWalker(component, func) {
  if (component.extends)
    componentWalker(component.extends, func);
  if (component.mixins)
    for (const mixin of component.mixins)
      componentWalker(mixin, func);
  func(component);
}

function readComponent(obj) {

}
/** 用于 Props 的类型构造函数 */
const propsType = {
  String: String,
  Number: Number,
  Object: Object,
  Function: Function,
  Boolean: Boolean,
  Array: Array,
}
/** 模块基类
 * @typedef {object} moduleBase
 * @property {string} name - 模块名
 * @property {string} note - 注释
 * @property {('const' | 'function' | 'constructor' | 'module' | 'object' | 'array' | 'component')} type - 模块类型
 */
/** 模块类型
 * @typedef {moduleBase | typeExample | (typeClass | typeEnum | typeTypes)} moduleType
 */
/** 类型示例值
 * 
 * 例如一个类型为 (string | { name: string, this: any }) 解析为可以是两个类型，示例值则是类型对应的值
 * 
 * 例如 string 类型的示例值为 "example"
 * 
 * 例如 object 类型的示例值为 { name: "example", this: undefined }
 * @typedef {object} typeExample
 * @property {} [example] - 示例值
 */
/** 单类型
 * @typedef {object} typeClass
 * @property {'types'} type - 模块类型
 * @property {(moduleBase | typeField)[]} value - 字段
 */
/** 类型字段
 * @typedef {object} typeField
 * @property {moduleType} type - 字段类型
 * @property {boolean} nullable - 是否可空
 * @property {} [default] - 默认值
 */
/** 枚举类型
 * 
 * 带 '' 的多类型被视为枚举类型例如 ('big' | 'small')
 * 
 * 用 keyof 的类型如 (keyof icons)，此时对象的 key 和 value 都是一样的字符串
 * @typedef {object} typeEnum
 * @property {'enum'} type - 模块类型
 * @property {(moduleBase | typeEnumValue)[]} value - 枚举值
 */
/** 枚举值
 * @typedef {object} typeEnumValue
 * @property {'const'} type - 模块类型
 * @property {string} value - 枚举值
 */
/** 多类型 | 交叉类型(两个类型都是类类型并且没加括号)
 * @typedef {object} typeTypes
 * @property {'or' | 'and'} type - 模块类型
 * @property {moduleType[]} value - 多个类型
 */
/** 交叉类型
 * 
 * (string | number) 代表类型的其中一个
 * 
 * (classA | classB) 两个类型都是类类型时，存在两种情况
 * 
 * 1. (classA | classB) 代表两个类型的字段同时拥有，即交叉类型
 * 
 * 2. ((classA | classB)) 代表类型的其中一个，带括号认为是选 1 个，不带括号是交叉类型
 * 
 * 3. (moduleBase | (typeClass | typeEnum | typeTypes)) 代表必有 moduleBase 字段，typeClass 和 typeEnum 和 typeTypes 中仅有其中 1 个类型的字段
 * 
 * 此时解析成 typeCross，包含两个类型
 * @typedef {object} typeCross
 * @property {'and'} type - 模块类型
 * @property {moduleType[]} value - 多个类型
 */
/** 模块常量
 * @typedef {object} moduleConst
 * @property {'const'} type - 模块类型
 * @property {number | string} value - 常量值
 */
/** 模块方法
 * @typedef {object} moduleFunction
 * @property {'function'} type - 模块类型
 * @property {boolean} [async] - 是否是异步方法
 * @property {commentProperty[]} params - 参数列表
 * @property {moduleType} [returns] - 返回类型
 */
/** 模块类型构造函数，主要是 vue 组件的 props 的类型用的构造函数来描述
 * 
 * (可以考虑不使用这个，直接在 props 里标注类型)
 * @typedef {object} moduleConstructor
 * @property {'constructor'} type - 模块类型
 * @property {string[]} constructors - 构造函数的类型
 */
/** 模块引用其它模块
 * @typedef {object} moduleModule
 * @property {'module'} type - 模块类型
 * @property {string} file - 引用的模块文件
 * @property {string} module - 引用的模块名
 */
/** 模块对象
 * @typedef {object} moduleObject
 * @property {'object'} type - 模块类型
 */
/** 模块数组
 * @typedef {object} moduleArray
 * @property {'array'} type - 模块类型
 */
/** 模块 vue 组件
 * @typedef {object} moduleComponent
 * @property {'component'} type - 模块类型
 * @property {(moduleBase | cProp)[]} props - Props
 */
/** vue 组件 props
 * @typedef {object} cProp
 * @property {boolean} model - 是否是 v-model
 * @property {} [default] - 默认值
 * @property {moduleType} type - 字段类型
 * @property {boolean} [required] - 必填
 */
/** vue 组件 emits
 * @typedef {object} cEmits
 * @property {(moduleBase | parameter)[]} params - 事件函数类型
 */
/** 方法参数
 * @typedef {object} parameter
 * @property {moduleType} type - 参数类型
 * @property {boolean} nullable - 是否可选参数
 */
/** 读取 export 模块
 * @param {String} name - 字段名
 * @param {*} obj - 字段值
 * @param {CommentReader} reader - 注释读取器
 * @returns {moduleBase | (moduleConst | moduleFunction | moduleConstructor | moduleObject | moduleArray | moduleComponent)} 模块
 */
function readModule(name, obj, reader) {
  if (obj) {
    if (typeof obj == 'string') {
      if (obj.startsWith('@module:')) {
        const index = obj.lastIndexOf('.');
        // 模块引用
        return {
          name,
          type: 'module',
          file: obj.substring(8, index),
          module: obj.substring(index + 1),
        }
      } else if ((obj.startsWith('function ') || obj.startsWith('async function ')) && obj.endsWith(')')) {
        // 方法(参数个数)
        const index1 = obj.indexOf('function ');
        const index2 = obj.indexOf('(', index1);
        const fname = obj.substring(index1 + 9, index2).trim();
        const param = obj.substring(index2 + 1, obj.indexOf(')', index2));
        if (propsType[fname]) {
          // props: String => function String(1) (类型)
          return {
            name,
            type: 'constructor',
            constructor: fname,
          }
        } else {
          return {
            name: name || fname,
            type: 'function',
            async: obj.startsWith('async function '),
            param,
          }
        }
      }
    } else if (obj.constructor == Object) {
      const clone = {};
      if (obj.__file && obj.__hmrId) {
        // todo: 组件：props | emits | slots | exposes(data & computed & methods)
        // todo: 注意：有继承或混入时，注释应该按照继承顺序读取，没有时往上查找
        // vue 组件
        clone.type = 'component';
        clone.note = reader.read(i => i.longname == 'module.exports')?.description;
        clone.name = obj.name;
        clone.props = [];
        clone.emits = [];
        clone.data = [];
        clone.computed = [];
        clone.methods = [];
        clone.slots = [];
        // todo: 有继承关系时，直接将继承对象的字段和注释加入
        // todo: __props 对象的键的顺序和代码定义的顺序不一致
        // todo: 尝试使用 componentWalker 在这里而不是在浏览器来处理继承关系
        for (const key in obj.__props) {
          const item = {};
          item.name = key;
          const note = reader.read(i => i.memberof == 'module.exports.props' && i.name == key, true);
          if (note) {
            item.note = note.description;
            if (note.examples) {
              item.examples = note.examples;
            }
          }
          // todo: item.type: props 对象的 default 注释的类型 / props 注释的类型 / props 值的类型|类型数组，对象的 type 字段的类型|类型数组
          item.model = obj.__emits[`update:${key}`];
          item.default = obj.__props[key];
          // json 序列化前将 undefined 转换成了 null 以保留字段名
          if (item.default == null)
            item.default = undefined;
          clone.props.push(item);
        }
        for (const key in obj.__emits) {
          const item = readModule(key, obj.__emits[key], reader);
          clone.emits.push(item);
        }
        for (const key in obj.__slots) {
          const item = readModule(key, obj.__slots[key], reader);
          clone.slots.push(item);
        }
        for (const key in obj.__data) {
          const item = readModule(key, obj.__data[key], reader);
          clone.data.push(item);
        }
        for (const key in obj.__computed) {
          const item = readModule(key, obj.__computed[key], reader);
          clone.computed.push(item);
        }
        for (const key in obj.__methods) {
          const item = readModule(key, obj.__methods[key], reader);
          clone.methods.push(item);
        }
        return {
          name: obj.name,
          type: 'component',
          value: clone,
        }
      } else {
        // 普通对象
        for (const key in obj) {
          const value = obj[key];
          clone[key] = readModule(key, value, reader);
        }
        return {
          name,
          type: 'object',
          value: clone,
        }
      }
    } else if (obj instanceof Array) {
      const clone = [];
      for (const i in obj)
        clone.push(readModule(i, obj[i], reader));
      return {
        name,
        type: 'array',
        value: clone,
      }
    }
  }
  return {
    name,
    type: 'const',
    value: obj,
  }
}

const comments = JSON.parse(fs.readFileSync('comments.json', 'utf-8'));
for (const file in comments) {
  for (const comment of comments[file]) {
    if (comment.description)
      comment.description = comment.description.replaceAll('\r\r', '\r').replaceAll('\r', '\n');
    if (comment.examples)
      comment.examples = comment.examples.map(i => i.replaceAll('\r\r', '\r').replaceAll('\r', '\n'));
  }
}
const modules = JSON.parse(fs.readFileSync('objects.json', 'utf-8'));

/** js-doc 注释
 * @typedef {object} comment
 * @property {string} comment - 原始注释
 * @property {('kind' | 'typedef' | 'constant')} kind - 注释类型
 * @property {('global' | 'static' | 'instance' | 'inner')} scope - 注释对应字段的作用域
 * @property {string} name - 注释对应字段名
 * @property {string} memberof - 对象里的字段，则是所属对象的名字
 * @property {string} longname - memberof + name
 * @property {string} description - 注释内容
 * @property {commentProperty[]} properties - 定义接口类型时的属性
 * @property {commentProperty[]} params - 定义委托类型时的参数
 * @property {commentProperty[]} returns - 定义委托类型时的返回值
 */
/** js-doc 参数或属性
 * @typedef {object} commentProperty
 * @property {string} name - 属性名
 * @property {boolean} optional - 是否可选
 * @property {string} description - 注释内容
 * @property {{names:string[]}} type - 数据类型
 */
class CommentReader {
  constructor(comments) {
    this.comments = comments;
    this.index = 0;
  }

  /** 读取注释
   * @param {function(comment):boolean} func - 注释是否是目标
   * @param {boolean} [peek] - 读取指针不往前走
   * @returns {comment} - 找到的注释
   */
  read(func, peek) {
    for (let i = this.index; i < this.comments.length; i++) {
      if (func(this.comments[i])) {
        if (!peek)
          this.index = i + 1;
        return this.comments[i];
      }
    }
  }

  /** 根据名字找注释 */
  readByName(name, memberof) {
    const quotName = `"${name}"`;
    return this.read(i => (i.name == name || i.name == quotName) && (!memberof || i.memberof == memberof));
  }
}
/** 用于读取字符串
 * 
 * peek: 不移动光标
 * 
 * next: 移动光标
 */
class StringReader {
  /** 空格符 */
  WHITE_SPACE = " \t\n\r";
  /** 单词的分隔字符
   * 
   * 例如 xml 为 ' \t\n\r<>'
   * 
   * 例如 json 为 ' \t\n\r{}[],:"'
   * 
   * 例如 csv 为 '",\r'
   */
  WORD_BREAK = "";
  /** 
   * @param {string} str - 要读取的字符串
   * @param {string} wordBreak - 单词风格符
   */
  constructor(str, wordBreak) {
    this.str = str;
    this.pos = 0;
    if (wordBreak)
      this.WORD_BREAK = wordBreak;
  }

  get isEnd() { return this.pos >= this.str.length; }
  /** 一个字符 */
  get nextChar() {
    if (this.pos < this.str.length)
      return this.str[this.pos++];
  }
  /** 一个字符 */
  get peekChar() { return this.str[this.pos]; }
  /** 一个单词 */
  get peekWord() { return this.peek(this.WORD_BREAK); }
  /** 一行字符串 */
  get peekLine() { return this.peek("\n"); }

  /** 获取当前光标偏移一定索引位置的字符
   * @param {number} index - 当前光标的偏移值
   */
  offset(index) { return this.str[this.pos + index]; }

  /** 从当前位置读取直到出现某个字符之间的字符串，并且光标移动到目标位置
   * @param {string} [filter] - 出现某个字符就会停止，不传或 null 默认为 WORD_BREAK
   * @param {boolean} [eatFilter=true] - 出现目标字符时，是否让光标移动到目标字符之后
   * @param {boolean} [eatWhitespace=true] - 是否忽略前面可能存在的空白字符，默认为 true
   * @returns {string} 从当前位置到目标位置之间的字符串
   * @example
   * new StringReader('html>').Next('>') // 读取到 html
   */
  next(filter = this.WORD_BREAK, eatFilter = true, eatWhitespace = true) {
    if (eatWhitespace)
      this.eatWhitespace(filter);

    if (this.pos == this.str.length)
      return;

    const start = this.pos;
    while (filter.indexOf(this.peekChar) == -1) {
      this.pos++;
      if (this.pos == this.str.length)
        break;
    }

    const end = this.pos;
    if (this.pos < this.str.length && eatFilter)
      this.pos++;
    return this.str.substring(start, end);
  }

  /** 从当前位置读取直到出现某个字符之间的字符串，并且光标还保留在读取前的位置
   * @param {string} [filter] - 出现某个字符就会停止，不传或 null 默认为 WORD_BREAK
   * @returns {string} 从当前位置到目标位置之间的字符串
   */
  peek(filter = this.WORD_BREAK) { return this.next(filter); }

  /** 去掉空格字符，直到出现某个字符
   * @param {string} [filter] - 出现某个字符就会停止，不传或 null 默认为 WORD_BREAK
   */
  eatWhitespace(filter = this.WORD_BREAK) {
    while ((filter || !filter.includes(this.peekChar)) && this.WHITE_SPACE.includes(this.peekChar)) {
      this.pos++;
      if (this.pos >= this.str.length)
        break;
    }
    return this;
  }

  /** 读取一行字符串 */
  eatLine() { return this.next("\n"); }

  // 前面都是读取直到遇到某个字符
  // 后面都是读取直到某个单词

  /** 读取接下来的段落直到找到目标单词
   * @param {string} sign - 目标单词
   * @param {boolean} [after=true] - 光标停在单词之后
   * @param {boolean} [word=false] - 结果字符串是否包含目标单词
   */
  sign(sign, after = true, word = false) {
    const index = this.str.IndexOf(sign, this.pos);
    if (index != -1) {
      const result = this.str.Substring(this.pos, index + (word ? sign.Length : 0));
      this.pos = index + (after ? sign.Length : 0);
      return result;
    }
  }

  /** 判断接下来是否是某个单词
   * @param {string} sign - 目标单词
   * @param {number} [skip=0] - 跳过几个字符后开始查找
   */
  isSign(sign, skip = 0) {
    return this.str.startsWith(sign, this.pos + skip);
  }
}

// 对应代码对象和注释
// todo: 对象中的继承，可能是引用了另外一个模块的对象，对象需要根据字段和深度生成唯一类型
// todo: 或者在浏览器层面，将模块的引用关系特殊化
// 根模块包括：组件，对象(可当作类型)，方法，常量，@typedef(接口类型)，@callback(委托类型，文档中也是 @typedef)，枚举
const results = {};
/** 对于临时定义的对象类型 @type {{ popups: popupInstance[] }} JS-DOC 解析后名字只有个 Object
 * 需要使用 comment-parser 再从注释中读取出真正的类型
 * 对于临时定义的类型因为无法有注释，所以直接作为字符串类型处理
 * todo: 但是字段类型有可能又是特殊类型，可能还是要修改成类型
 * 类型主要是 typedef | type | param | property 用到，无论哪种，类型都带有 {}
 * 所以字符串传入一整行的字符串，方法会找到并解析类型的部分
 * @param {string} type
 */
function findOriginType(type) {
  let index = type.indexOf("{");
  if (index < 0)
    throw '类型字符串应该包含 { 字符';
  const reader = new StringReader(type, "|(){}");
  const stack = [0];
  const types = [];
  reader.next('{');
  debugger
  while (true) {
    switch (reader.eatWhitespace().peekChar) {
      case '{':
        stack.push(0);
        const newType = {};
        let open = 1;
        while (true) {
          const key = reader.next(':');
          let value = reader.next(',}', false).trim();
          const open2 = value.split('{').length;
          if (open2 == 1) {
            // 中间没有包含 { 字符，也就是字段值的类型不是个对象类型
            newType[key] = findOriginType(value);
          } else {
            // 中间包含 { 字符，也就是字段值的类型包含至少一个对象类型
            // 这里要跳过中间的对象类型，找到属于自己的 }
            while (--open2 > 0)
              value += reader.next('}').trim();
            value += reader.next(',}', false).trim();
            newType[key] = findOriginType(value);
          }
          if (reader.nextChar == '}')
            break;
          // else ',' 代表可能还有其它字段
        }
        types.push(newType);
        break;
      case ')}': stack.pop(); break;
      default:
        const typeName = reader.next().trim();
        types.push(typeName);
        break;
    }
  }
}
// findOriginType(`/** el\r\n * @typedef {HTMLElement | {__eet: data | eetdata}} el\r\n */`);
// 类型名要能唯一对应类型，即便是不同文件的类型
const typeShortName = {};
const unknowTypes = {};
// 从注释读取 @typedef 类型
// todo: function 和 Object 类型可能缺少了实际类型
// 例如 function(string):number 会只剩下 function，需要使用 doctrine 再从注释中读取出真正的类型
// todo: 对于这种临时定义的属性 @type {{ popups: popupInstance[] }} JS-DOC 解析后名字只有个 Object
for (const file in comments) {
  const _comments = comments[file];
  const reader = new CommentReader(_comments);
  while (true) {
    const comment = reader.read(i => i.kind == 'typedef');
    if (!comment)
      break;
    const type = {
      name: comment.name,
      note: comment.description,
    };
    if (!comment.type || comment.type.names.length == 1) {
      // 单类型
      let typeName = comment.type?.names[0] || 'object';
      // 枚举类型，需要读取对象模块的所有 key
      if (typeName.startsWith('keyof_')) {
        typeName = typeName.substring(6);
        // 一般声明枚举都是在对象所在文件中
        const enums = modules[file][typeName];
        if (!enums || enums.constructor != Object) {
          console.error(`${file} @typedef { keyof ${typeName} } ${comment.name} 中的 ${typeName} 未找到或不是正确的 { key: key } 对象`);
          continue;
        }
        type.type = 'enum';
        type.value = [];
        // 对于 ('left' | 'top') 这样的，value 就是值数组，keyof 兼容这种格式
        for (const key in enums) {
          const value = {
            name: key,
            // todo: 这里应该是直接获取模块的注释
            note: reader.readByName(key, typeName)?.description,
          };
          type.value.push(value);
        }
      } else if (typeName == 'function') {
        // @callback 定义委托类型
        type.type = 'function';
        type.value = [];
        if (comment.params) {
          for (const property of comment.params) {
            type.value.push({
              name: property.name,
              note: property.description,
              nullable: property.optional,
              type: [...property.type.names]
            })
          }
        }
        if (comment.returns) {
          for (const property of comment.returns) {
            type.value.push({
              name: 'return',
              note: property.description,
              type: [...property.type.names]
            })
          }
        }
        // console.log("@callback 定义 function 类型", comment.name);
      } else if (typeName == 'object') {
        // @typedef {object} 定义 class 类型
        type.type = 'class';
        type.value = [];
        if (comment.properties) {
          for (const property of comment.properties) {
            type.value.push({
              name: property.name,
              note: property.description,
              nullable: property.optional,
              type: [...property.type.names]
            })
          }
        }
        // console.log("@typedef 定义 object 类型", comment.name);
      } else if (typeName == 'Object') {
        // 对于临时定义的对象类型 @type {{ popups: popupInstance[] }} JS-DOC 解析后名字只有个 Object
        // 需要使用 doctrine 再从注释中读取出真正的类型
        // 对于临时定义的类型因为无法有注释，所以直接作为字符串类型处理
        const c2 = doctrine.parse(comment.comment);
        debugger;
        console.log(c2);
      } else {
        // 引用其它类型
        // 例如 Object.<string, field | field[]>
        // 例如 DirectiveHook
        type.type = 'type';
        type.value = typeName;
        // console.log("引用其它类型？", typeName)
      }
    } else {
      // 多类型
      type.value = [];
      // 枚举例如 ('left' | 'right')
      if (comment.type.names.some(i => i.startsWith('\'') && i.endsWith('\''))) {
        type.type = 'enum';
        for (const item of comment.type.names) {
          type.value.push({
            name: item.startsWith('\'') && item.endsWith('\'') ? item.slice(1, -1) : item
          })
        }
      } else {
        type.type = 'types';
        // 主要是引用其它类型
        for (const index in comment.type.names) {
          let item = comment.type.names[index];
          if (item == 'Object') {
            // 对于临时定义的对象类型 @type {{ popups: popupInstance[] }} JS-DOC 解析后名字只有个 Object
            // 需要使用 doctrine 再从注释中读取出真正的类型
            // 对于临时定义的类型因为无法有注释，所以直接作为字符串类型处理
            item = cp.parse(comment.comment)[0].tags[0].type.split('|')[index];
            // 使用正则表达式匹配
            item = /{((?:[^{}]|{(?:[^{}]|{[^{}]*})*})*)}/g.exec(comment.comment)[1];
            comment.type.names[index] = item;
          }
          // 先记录名字，后面再根据名字匹配
          type.value.push(item);
        }
      }
    }
    const fullname = `${file}.${comment.name}`;
    if (typeShortName[type.name]) {
      console.warn(`类型重名 [${fullname}] = [${typeShortName[type.name]}]`);
    }
    typeShortName[type.name] = fullname;
    results[fullname] = type;
  }
}
const jstypes = ['object', 'string', 'number', 'boolean', 'undefined']
/** 将类型名的字符串修改成模块类型的引用
 * @param {string} typeName - 类型名字
 * @returns {string} 基础类型保持原始的字符串，例如 string, object；模块类型改为模块全名，例如 option 改为 ./form/index.js.option
 */
function resolveType(typeName) {
  const lower = typeName.toLowerCase();
  if (jstypes.includes(lower))
    return lower;
  if (lower == '*' || lower == 'any')
    return 'any';
  // Object.<string, option[]> | Record.<string, option[]>
  if (typeName.startsWith('Object.<') || typeName.startsWith('Record.<')) {
    const index = typeName.indexOf(',');
    const type1 = resolveType(typeName.substring(8, index));
    const type2 = resolveType(typeName.substring(index + 1, typeName.lastIndexOf('>')).trim());
    return `Object<${type1}, ${type2}>`;
  }
  // Array.<optionObject>
  if (typeName.startsWith('Array.<')) {
    const type1 = resolveType(typeName.substring(7, typeName.lastIndexOf('>')));
    return `${type1}[]`;
  }
  // Promise | Promise.<Array.<optionObject>>
  if (typeName.startsWith('Promise')) {
    if (typeName.startsWith('Promise.')) {
      const type1 = resolveType(typeName.substring(9, typeName.lastIndexOf('>')));
      return `Promise<${type1}>`;
    } else {
      return 'Promise<any>';
    }
  }
  // 多类型(string | number)
  if (typeName.startsWith('(') && typeName.endsWith(')')) {
    const types = typeName.slice(1, -1).split('|');
    for (let i = 0; i < types.length; i++)
      types[i] = resolveType(types[i]);
    return types.join(' | ');
  }
  // todo: 临时类型
  if (typeName.startsWith('{') && typeName.endsWith('}')) {
    typeName = typeName.slice(1, -1);
    let index = 0;
    const fields = [];
    while (true) {
      const index2 = typeName.indexOf(':', index);
      fields.push({
        name: typeName.substring(index, index2),
      })
    }
  }
  // todo: 临时方法
  if (typeName.startsWith('function')) {
  }
  // 其它未知类型，例如 泛型T ，非自定义模块的类型 HTMLElement 等
  if (typeShortName[typeName])
    return typeShortName[typeName];
  // 可能是临时枚举 '1' | '2' | '3'
  if (!unknowTypes[typeName] && !typeName.startsWith("'") && !typeName.endsWith("'")) {
    console.log('未知类型', typeName);
    unknowTypes[typeName] = typeName;
    if (typeName == 'function')
      debugger;
  }
  return typeName;
}
// 将所有模块生成类型文档
for (const file in comments) {
  const _comments = comments[file];
  const _modules = modules[file];
  for (const moduleName in _modules) {
    const module = _modules[moduleName];
    results[`${file}.${moduleName}`] = readModule(moduleName, module, new CommentReader(_comments));
  }
}
// 将所有类型的字段引用到模块类型上
for (const key in results) {
  const type = results[key];
  if (type.type == 'type') {
    type.value = resolveType(type.value);
  } else if (type.type == 'types') {
    for (const key in type.value) {
      type.value[key] = resolveType(type.value[key]);
    }
  } else if (type.type == 'function' || type.type == 'class') {
    for (const item of type.value) {
      for (const key in item.type) {
        item.type[key] = resolveType(item.type[key]);
      }
    }
  }
}
fs.writeFileSync('ee-ui.doc.json', JSON.stringify(results));
return;

const options = process.argv;
// 当前cmd执行的路径
const cwd = process.cwd();

const config = {
  /** 要读取的文件夹 */
  root: cwd + '/src',
  /** 要注入的 js 脚本 */
  main: cwd + '/src/main.js',
  /** 要读取的文件后缀，用 '|' 分隔，例如 .js|.vue */
  suffix: '.js|.vue',
}
config.root = options[2] || config.root;
config.main = options[3] || config.main;
config.suffix = options[4] || config.suffix;

if (!fs.existsSync(config.main)) {
  console.error("缺少代码注入入口文件", config.main);
  return;
}

let main = fs.readFileSync(config.main, 'utf-8');
{
  const index = main.indexOf('// </vue-doc-generator>');
  if (index >= 0) {
    main = main.substring(index + '// </vue-doc-generator>'.length + 1);
  }
}
const suffix = config.suffix.split('|');

// 递归函数，用于遍历文件夹及其子文件夹
function readFilesInFolder(folderPath, ast) {
  // 读取文件夹中的文件和子文件夹
  const files = fs.readdirSync(folderPath);

  // 遍历文件和子文件夹
  files.forEach(file => {
    // 获取文件/文件夹的完整路径
    const filePath = path.join(folderPath, file);

    // 判断是否为文件夹
    if (fs.statSync(filePath).isDirectory()) {
      // 如果是文件夹，则递归调用 readFilesInFolder 函数
      readFilesInFolder(filePath, ast);
    } else {
      // 如果是文件，并且是 .js 文件，则进行处理
      if (suffix.includes(path.extname(filePath))) {
        // 在这里可以对文件进行处理，例如读取文件内容等
        const rp = `./${path.relative(config.root, filePath).replaceAll('\\', '/')}`;
        let comment = "";
        switch (path.extname(filePath)) {
          case ".vue":
            const code = fs.readFileSync(filePath, 'utf-8');
            const index = code.indexOf('<script');
            if (index >= 0) {
              const index2 = code.indexOf('</script>', index);
              comment = code.substring(code.indexOf('>', index) + 1, index2);
            } else {
              console.log("skip file", filePath);
              return;
            }
            break;

          case ".js":
          case ".jsx":
            comment = fs.readFileSync(filePath, 'utf-8');
            break;

          default:
            console.log("skip file", filePath);
            return;
        }
        if (!comment)
          return;
        console.log('parse comment', rp)
        // comment-parse
        // 1. 保留原始类型，方便与运行时类型对应
        // jsdoc
        // 1. 包含作用域，方便与运行时对象对应
        // doctrine
        // 1. 只解析注释，不解析 js 代码
        // 2. 能将类型解析得更详细，不用自己从 comment-parse 解析之后的原始类型再解析一遍
        const result = {};

        comment = comment.replaceAll('{keyof ', '{keyof_');
        /**
         * @type {jsdocComment[]}
         */
        const jd = jsdoc.explainSync({ source: comment });
        // jsdoc 解析会报错的一些规范
        // @type {function((number | string))}：方法中参数有多个类型时，参数类型必须加上 ()
        // @type {keyof icons}：keyof 不能识别，转换成 keyof_ 后面使用时再转回来
        for (const item of jd) {
          // 只有 function 类型时才需要 doctrine 再解析一遍
          // const dc = doctrine.parse(comment, { unwrap: true });
          if (!item.comment)
            continue;
        }
        //ast[rp] = ;
        ast[rp] = jd;
      }
    }
  });
}

function getCommentType(comment) {
  if (comment.kind != 'typedef')
    throw '注释类型必须是 typedef 现在是 ' + comment.kind;
  const obj = {
    name: comment.name,
    note: comment.description,
  };
  // todo: 类型需要引用到模块
  if (comment.type.names.length > 1) {
    // 多类型
    if (comment.type.names.includes('function')) {
      // function 类型需要特殊处理获取其参数个数
    } else {
      obj.type = 'types';
      obj.types = comment.type.names;
    }
  } else {
    if (comment.properties) {
      // @typedef {object}
    } else if (comment.param) {

    }
  }
}

function readComment(objects) {
  console.log('readComment');
  fs.writeFileSync(config.main, main);
  fs.writeFileSync('objects.json', JSON.stringify(objects));
  const ast = {};
  readFilesInFolder(config.root, ast);
  fs.writeFileSync('comments.json', JSON.stringify(ast));
  // 对应代码对象和注释
  // todo: 对象中的继承，可能是引用了另外一个模块的对象，对象需要根据字段和深度生成唯一类型
  // todo: 或者在浏览器层面，将模块的引用关系特殊化
  const modules = {};
  const modulesByFile = {};

  process.exit(0);
}

const http = require('http');
const { debug } = require('console');

function listen(port) {
  // 注入代码，主要是发送接口的端口
  // 注入的代码的作用
  // 1. 获取项目中所有导出的模块
  // 2. 将模块中的方法替换成 function(参数个数)
  // 3. 将所有模块通过网络发给服务器以生成文档
  const inject = `// <vue-doc-generator>
import { createApp as vdgCreateApp } from 'vue';
;(function() {
  // 加载目标文件夹内的所有 export 的模块
  const components = require.context('${path.relative(path.dirname(config.main), config.root).replace(/\\/g, '/')}', true, /\\${config.suffix}\$/)
  const modules = {};
  const refs = {};
  components.keys().forEach(i => {
    const component = components(i);
    const obj = {};
    modules[i] = obj;
    for (const key in component) {
      obj[key] = component[key];
    }
  })
  // 按照 vue 的构造顺序遍历组件
  function componentWalker(component, func) {
    if (component.extends)
      componentWalker(component.extends, func);
    if (component.mixins)
      for (const mixin of component.mixins)
        componentWalker(mixin, func);
    func(component);
  }
  // 将引用的模块替换成特殊字符串，以保留其引用关系
  function mayRef(value) {
    if (value != null && value instanceof Object) {
      for (const file in modules) {
        const _file = modules[file];
        for (const _export in _file)
          if (value === _file[_export])
            return \`@module:\${ file }.\${ _export }\`;
      }
    }
    return transform(value);
  }
  // 克隆模块，方法仅保留声明
  function transform(value) {
    if (value instanceof Array) {
      const array = [];
      for (const key of value)
        array.push(mayRef(key));
      return array;
    } else if (value instanceof Function) {
      return \`\${value.constructor.name == 'AsyncFunction' ? 'async ' : ''}function \${ value.name } (\${ value.length })\`;
    } else if (value?.constructor == Object) {
      const copy = {};
      for (const key in value)
        copy[key] = mayRef(value[key]);
      return copy;
    } else {
      return value;
    }
  }
  // 克隆 data 和 props 用，将 undefined 转换成 null 以便 json 序列化保留字段
  function cloneObject(copy) {
    const clone = {};
    for (const key in copy)
      clone[key] = copy[key] === undefined ? null : copy[key];
    return clone;
  }
  // 将模块转换成可序列化和反序列化的对象
  const result = {};
  for (const file in modules) {
    const _file = modules[file];
    const clone = {};
    for (const _export in _file) {
      const module = _file[_export];
      clone[_export] = transform(module);
      if (module.__file && module.__hmrId) {
        const component = clone[_export];
        // 将 props | emits | slots | exposes(data & computed & methods) 全部生成，忽略 extends 和 mixins
        component.__emits = {};
        component.__slots = {};
        component.__computed = {};
        component.__methods = {};
        componentWalker(module, (i) => {
          if (i.emits) {
            if (i.emits instanceof Array)
              for (const value of i.emits)  
                component.__emits[value] = 'function (1)'
            else
              for (const key in i.emits)
                component.__emits[key] = transform(i.emits[key]);
          }
          if (i.slots) {
            for (const key in i.slots)
              component.__slots[key] = cloneObject(i.slots[key]);
          }
          if (i.computed) {
            for (const key in i.computed)
              component.__computed[key] = transform(i.computed[key]);
          }
          if (i.methods) {
            for (const key in i.methods)
              component.__methods[key] = transform(i.methods[key]);
          }
        })
        // 生成一个组件实例，获得 data 和 props 字段的默认值
        const div = document.createElement('div');
        div.id = 'vdg' + module.__hmrId;
        div.setAttribute('name', module.name);
        try {
          document.body.appendChild(div);
          vdgCreateApp({
            extends: module,
            created() {
              if (this.$data)
                component.__data = cloneObject(this.$data);
              if (this.$props)
                component.__props = cloneObject(this.$props);
              div.setAttribute('vdg', '');
            }
          }).mount('#vdg' + module.__hmrId);
        } catch (err) {
          const log = div.hasAttribute('vdg') ? console.warn : console.error;
          log('创建模块实例异常', module.__file, err);
        }
      }
    }
    result[file] = clone;
  }
  // 将模块发送给服务端
  const xhr = new XMLHttpRequest();
  xhr.open('POST', 'http://localhost:${port}/');
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.send(JSON.stringify(result));
  console.log(result);
})();
// </vue-doc-generator>
`;
  fs.writeFileSync(config.main, `${inject}${main}`);

  // 创建 HTTP 服务器
  const server = http.createServer((req, res) => {
    // 设置响应头允许跨域访问
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    // 处理请求
    if (req.method === 'OPTIONS') {
      // 如果是预检请求，直接返回 200
      res.statusCode = 200;
      res.end();
      return;
    }

    let body = '';

    // 监听请求事件，读取请求数据
    req.on('data', chunk => {
      body += chunk.toString();
    });

    // 监听请求结束事件，处理请求数据并返回响应
    req.on('end', () => {
      try {
        const data = JSON.parse(body);

        // 返回成功响应
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.write('vue-doc-generator');

        // todo:
        // 1. 重新读取注释
        // 2. 注释和对象对应起来并生成文档
        readComment(data);
      } catch (err) {
        console.error('Failed to parse request data:', err);

        // 返回失败响应
        res.writeHead(400, { 'Content-Type': 'text/plain' });
        res.write('Failed to parse request data');
      }

      res.end();
    });
  });
  server.on('error', err => {
    console.error('Server error:', err);
    // 进行错误处理...
    setTimeout(() => listen(port++));
  });

  // 启动 HTTP 服务器
  server.listen(port, () => {
    console.log('Server is running on port', port);
  });
}

listen(3001);